﻿using JNPF.Data.Util;
using Microsoft.EntityFrameworkCore;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations.Schema;
using System.IO;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;

namespace JNPF.Data
{
    public class BaseDbContext : DbContext, IDisposable
    {
        public string dbConnString { get; set; }
        public DatabaseType dbType { get; set; }
        public BaseDbContext(string _connString, DatabaseType _dbType)
        {
            dbConnString = _connString;
            dbType = _dbType;
            InitEntityType();
        }
        protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        {
            switch (dbType)
            {
                case DatabaseType.SqlServer:
                    optionsBuilder.UseSqlServer(dbConnString);
                    break;
                case DatabaseType.Oracle:
                    optionsBuilder.UseOracle(dbConnString, b => b.UseOracleSQLCompatibility("11"));
                    break;
                case DatabaseType.MySql:
                    optionsBuilder.UseMySQL(dbConnString);
                    break;
                default:
                    break;
            }
            base.OnConfiguring(optionsBuilder);
        }
        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            var entityMaps = _entityTypeMap.Values.ToList();
            entityMaps.ForEach(x =>
            {
                modelBuilder.Entity(x);
                ////主键
                //var keys = x.GetCustomAttribute<KeysAttribute>();
                //if (keys != null)
                //    entityTypeBuilder.HasKey(keys.PropertyNames);

                ////索引
                //var indexs = x.GetCustomAttributes<IndexAttribute>();
                //if (indexs != null)
                //{
                //    indexs.ToList().ForEach(aIndex =>
                //    {
                //        entityTypeBuilder.HasIndex(aIndex.PropertyNames).IsUnique(aIndex.IsUnique);
                //    });
                //}
            });
            base.OnModelCreating(modelBuilder);
        }
        public IQueryable GetIQueryable(Type entityType)
        {
            var dbSet = this.GetType().GetMethod("Set").MakeGenericMethod(entityType).Invoke(this, null);
            var resQ = typeof(EntityFrameworkQueryableExtensions).GetMethod("AsNoTracking").MakeGenericMethod(entityType).Invoke(null, new object[] { dbSet });
            return resQ as IQueryable;
        }
        public void Detach()
        {
            ChangeTracker.Entries().ToList().ForEach(aEntry =>
            {
                if (aEntry.State != EntityState.Detached)
                    aEntry.State = EntityState.Detached;
            });
        }
        public override int SaveChanges()
        {
            int count = base.SaveChanges();
            Detach();
            return count;
        }
        public async override Task<int> SaveChangesAsync(CancellationToken cancellationToken = default)
        {
            int count = await base.SaveChangesAsync(cancellationToken);
            Detach();
            return count;
        }
        private static void InitEntityType()
        {
            List<Type> types = AllEntityTypes
                .Where(x => x.GetCustomAttribute(typeof(TableAttribute), false) != null)
                .ToList();
            types.ForEach(aType =>
            {
                _entityTypeMap[aType.Name] = aType;
            });
        }

        #region 私有成员
        private static ConcurrentDictionary<string, Type> _entityTypeMap { get; } = new ConcurrentDictionary<string, Type>();
        private static List<Type> _allEntityTypes;
        private static object _lock = new object();
        internal static List<string> AssemblyNames = new List<string>();
        internal static List<string> AssemblyPaths = new List<string>() { AppDomain.CurrentDomain.BaseDirectory };
        internal static List<Type> AllEntityTypes
        {
            get
            {
                if (_allEntityTypes == null)
                {
                    lock (_lock)
                    {
                        if (_allEntityTypes == null)
                        {
                            _allEntityTypes = new List<Type>();

                            Expression<Func<string, bool>> where = x => true;
                            where = where.And(x =>
                                  !x.Contains("System.")
                                  && !x.Contains("Microsoft."));
                            if (AssemblyNames.Count > 0)
                            {
                                Expression<Func<string, bool>> tmpWhere = x => false;
                                AssemblyNames.ToList().ForEach(aAssembly =>
                                {
                                    tmpWhere = tmpWhere.Or(x => x.Contains(aAssembly));
                                });

                                where = where.And(tmpWhere);
                            }
                            AssemblyPaths.SelectMany(x => Directory.GetFiles(x, "*.dll"))
                                .Where(x => where.Compile()(new FileInfo(x).Name))
                                .Distinct()
                                .Select(x =>
                                {
                                    try
                                    {
                                        return Assembly.LoadFrom(x);
                                    }
                                    catch
                                    {
                                        return null;
                                    }
                                })
                                .Where(x => x != null && !x.IsDynamic)
                                .ForEach(aAssembly =>
                                {
                                    try
                                    {
                                        _allEntityTypes.AddRange(aAssembly.GetTypes());
                                    }
                                    catch
                                    {

                                    }
                                });
                        }
                    }
                }
                return _allEntityTypes;
            }
        }
        #endregion
    }
}